def wolfe(f, g, x0, d, f0, g0, c1, c2, a_init, a_max, max_iter):
    """
    Perform Wolfe line search on specified function

    :param f: objective function
    :param g: gradient of f
    :param x0: starting point
    :param d: search direction
    :param f0: f(x0)
    :param g0: g(x0)
    :param c1: sufficient decrease factor
    :param c2: curvature factor
    :param a_init: initial step length
    :param a_max: max step length
    :param max_iter: max iterations
    :return: (a, iterations)
    """

    def fk(a):
        return f(x0 + a * d)

    def gk(a):
        return g(x0 + a * d)

    g0td = g0 @ d
    if g0td >= 0:
        print('###WARNING: not descent direction, g0^Td=%f' % g0td)
    if c1 > c2 or c1 < 0 or c2 > 1:
        print('###WARNING: bad argument, line search may not converge')
    c1g0td = c1 * g0td
    c2g0td = c2 * g0td
    a1 = 0
    a2 = a_init
    has_initial_range = False
    for it in range(max_iter):
        if has_initial_range:
            ak = (a1 + a2) * 0.5
            if fk(ak) > f0 + ak * c1g0td:
                a2 = ak
            elif (gk(ak) @ d) < c2g0td:
                a1 = ak
            else:
                return ak, it + 1
        else:
            ak = a2
            if fk(ak) > f0 + ak * c1g0td:
                has_initial_range = True
            elif (gk(ak) @ d) < c2g0td:
                a2 = ak * 2
                if a2 >= a_max:
                    a2 = a_max
                    has_initial_range = True
            else:
                return ak, it + 1
    print('###WARNING: line search failed to converge')
    return (a1 + a2) * 0.5, max_iter
